#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#define _XOPEN_SOURCE 700

#define MAX_ITERATIONS 10  // 控制测试次数

// 定义三种材料组合信号量
enum { SP, SG, PG };

// 共享内存结构体
struct shared_memory {
    int finish;  // 完成标志
    int iterations;
};

// P/V操作联合体
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

// 初始化信号量
int init_sem(int sem_id, int value) {
    union semun sem_union;
    sem_union.val = value;
    return semctl(sem_id, 0, SETVAL, sem_union);
}

// P操作
void sem_p(int sem_id) {
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = -1;
    sem_b.sem_flg = SEM_UNDO;
    semop(sem_id, &sem_b, 1);
}

// V操作
void sem_v(int sem_id) {
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = 1;
    sem_b.sem_flg = SEM_UNDO;
    semop(sem_id, &sem_b, 1);
}

// 供应者进程
void provider(int sem_set_id, struct shared_memory *shm) {
    int current_material = SP;
    while (shm->iterations < MAX_ITERATIONS) {
        // 轮流提供三种组合
        switch (current_material) {
            case SP:
                sem_v(sem_set_id + SP);  // 提供烟草+纸
                printf("[供应者] 提供烟草和纸\n");
                break;
            case SG:
                sem_v(sem_set_id + SG);  // 提供烟草+胶水
                printf("[供应者] 提供烟草和胶水\n");
                break;
            case PG:
                sem_v(sem_set_id + PG);  // 提供纸+胶水
                printf("[供应者] 提供纸和胶水\n");
                break;
        }
        
        current_material = (current_material + 1) % 3;
        shm->iterations++;
        
        // 等待抽烟者完成
        sem_p(sem_set_id + 3);  // 使用第四个信号量作为完成信号
    }
}

// 抽烟者进程（参数0:烟草，1:纸，2:胶水）
void smoker(int id, int sem_set_id, struct shared_memory *shm) {
    while (shm->iterations < MAX_ITERATIONS) {
        // 根据ID等待对应材料组合
        switch (id) {
            case 0:  // 有烟草，需要纸+胶水
                sem_p(sem_set_id + PG);
                break;
            case 1:  // 有纸，需要烟草+胶水
                sem_p(sem_set_id + SG);
                break;
            case 2:  // 有胶水，需要烟草+纸
                sem_p(sem_set_id + SP);
                break;
        }

        printf("[抽烟者%d] 获得材料，开始卷烟...\n", id);
        sleep(1);  // 模拟卷烟时间
        printf("[抽烟者%d] 完成吸烟\n", id);
        
        // 通知供应者
        sem_v(sem_set_id + 3);
    }
}

int main() {
    // 创建信号量集合（4个：3种组合+1完成信号）
    int sem_set_id = semget(IPC_PRIVATE, 4, 0666 | IPC_CREAT);
    init_sem(sem_set_id + SP, 0);  // 烟草+纸
    init_sem(sem_set_id + SG, 0);  // 烟草+胶水
    init_sem(sem_set_id + PG, 0);  // 纸+胶水
    init_sem(sem_set_id + 3, 0);   // 完成信号

    // 创建共享内存
    int shm_id = shmget(IPC_PRIVATE, sizeof(struct shared_memory), 0666 | IPC_CREAT);
    struct shared_memory *shm = (struct shared_memory*)shmat(shm_id, 0, 0);
    shm->finish = 0;
    shm->iterations = 0;

    // 创建三个抽烟者进程
    for (int i = 0; i < 3; i++) {
        if (fork() == 0) {
            smoker(i, sem_set_id, shm);
            exit(0);
        }
    }
    sleep(1);
    // 创建供应者进程
    if(fork()==0)
    {
      provider(sem_set_id,shm);
      exit(0);
    }
    // 等待所有子进程结束
    for (int i = 0; i < 4; i++) wait(NULL);

    // 清理资源
    semctl(sem_set_id, 0, IPC_RMID);
    shmdt(shm);
    shmctl(shm_id, IPC_RMID, 0);

    return 0;
}
